/**
 * @file winbond_spi.c
 * @brief Winbond Compatible SPI Flash Driver
 *
 * @author yemt@witsi.cn
 * @date 2012-06-13
 * @version Winbond.01
 */
#include <stdint.h>
#include "dev.h"
#include "flash.h"
#include "init.h"
#include "kernel.h"
#include "config.h"

#define CMD_W25_WREN            0x06    /* Write Enable */
#define CMD_W25_WRDI            0x04    /* Write Disable */
#define CMD_W25_RDSR            0x05    /* Read Status Register */
#define CMD_W25_WRSR            0x01    /* Write Status Register */
#define CMD_W25_READ            0x03    /* Read Data Bytes */
#define CMD_W25_FAST_READ       0x0b    /* Read Data Bytes at Higher Speed */
#define CMD_W25_PP              0x02    /* Page Program */
#define CMD_W25_SE              0x20    /* Sector (4K) Erase */
#define CMD_W25_BE              0xd8    /* Block (64K) Erase */
#define CMD_W25_CE              0xc7    /* Chip Erase */
#define CMD_W25_DP              0xb9    /* Deep Power-down */
#define CMD_W25_RES             0xab    /* Release from DP, and Read Signature */
#define CMD_W25_JEDEC_ID        0x9F

#define WINBOND_SR_WIP          (1 << 0)        /* Write-in-Progress */


/*
 * Winbond SPI Flash Parameters
 */
struct winbond_spi_flash_params {
        uint16_t        id;             /* Winbond Flash ID Code */
        uint8_t         page_shift;     /* Page Shift, for page size */
        uint16_t        sector_shift;   /* Sector Shift, for erase unit */
        uint16_t        block_shift;    /* Block Shift, unused now */
        uint16_t        nr_blocks;      /* Block Numbers, unused now */
        const char      *name;          /* Flash Chip Name in Winbond */
};
/*
 * Supported Flash table
 */
static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
        {
                .id                     = 0x4015,
                .page_shift             = 8,
                .sector_shift           = 12,
                .block_shift            = 16,
                .nr_blocks              = 32,
                .name                   = "x25Q16", /* x = w, GD */
        },
        {
                .id                     = 0x4016,
                .page_shift             = 8,
                .sector_shift           = 12,
                .block_shift            = 16,
                .nr_blocks              = 32,
                .name                   = "x25Q32FV", /* x = w, GD */
        },
        {
                .id                     = 0x2016,
                .page_shift             = 8,
                .sector_shift           = 12,
                .block_shift            = 16,
                .nr_blocks              = 64,
                .name                   = "MX25L3206E", /* x = w, GD */
        },
};


static int winbond_wait_ready(spi_slave_t *spi, unsigned long timout)
{
        uint32_t tstart;
        int ret;
        uint8_t status;
        uint8_t cmd[4] = { CMD_W25_RDSR, 0xff, 0xff, 0xff };

        if ((ret = spi_xfer(spi, &cmd[0], 4, NULL, SPI_XFER_BEGIN)) < 0) return ret;

        tstart = timeout_start();

        do {
                if ((ret = spi_xfer(spi, NULL, 1, &status, 0)) < 0) return ret;
                if ((status & WINBOND_SR_WIP) == 0)
                        break;
        } while (!timeout_test(&tstart, &timout));

        spi_xfer(spi, NULL, 0, NULL, SPI_XFER_END);

        if ((status & WINBOND_SR_WIP) == 0)
                return 0;
        return -1;
}

static int flash_probe_winbond(flash_t * fl)
{
        spi_slave_t * spi = fl->inf.spi;
        int ret;
        uint8_t idcode[3], cmd;
        uint16_t realid;
        const struct winbond_spi_flash_params *params;
        spi_param_t param;
	
        param.baud = CONFIG_PCLK1; /* max clock */
        param.bus_16bit = 0;
        param.cpha = 1;
        param.cpol = 1;
        param.lsb_mod = 0;
	    spi_register_slave(spi, &param);


        cmd = CMD_W25_JEDEC_ID;
        //cmd = 0xaa;

        if (spi_xfer(spi, &cmd, 1, NULL, SPI_XFER_BEGIN) < 0) {
                return -1;
        }
        if (spi_xfer(spi, NULL, 3, idcode, SPI_XFER_END) < 0) {
                return -1;
        }
        if (idcode[0] == 0xFF) {
                return -1;
        }
        realid = ((idcode[1] << 8) | idcode[2]);
        for (ret = 0; ret < ARRAY_SIZE(winbond_spi_flash_table); ret++) {
                params = &winbond_spi_flash_table[ret];
                if (params->id == realid)
                        break;
        }
        if (ret == ARRAY_SIZE(winbond_spi_flash_table)) {
                return -1;
        }
        fl->priv = (void *)params;
        return 0;
}

static int winbond_erase(flash_t * fl, uint32_t offset, size_t len)
{
        const struct winbond_spi_flash_params *params =
                (struct winbond_spi_flash_params *)(fl->priv);
        uint32_t sector_addr, actual;
        uint32_t sector_shift = params->sector_shift;
        uint8_t cmd[4];
        spi_slave_t * spi = fl->inf.spi;

        /* Erase offset/length must be multiple of sector size */
        if ((offset & ((1 << sector_shift) - 1)) ||
            (len & ((1 << sector_shift) - 1))) {
                return -1;
        }
        sector_addr = offset >> sector_shift;
        len >>= sector_shift;
        cmd[0] = CMD_W25_SE;
        cmd[3] = 0;
        /* always in sector bound, byte_addr == 0 */
        for (actual = 0; actual < len; actual ++) {
                cmd[1] = CMD_W25_WREN;
                /* WEL will be forced to 0 after erased, so reset to 1 again */
                if (spi_xfer(spi, cmd + 1, 1,
                                NULL, SPI_XFER_BEGIN|SPI_XFER_END) < 0)
                        return -1;
                /* sector addr won't be all in A23-A16, there must be some in A15-A17,
                 * but never in A7-A0, so we have the below shift:
                 */
                cmd[1] = sector_addr >> (16 - sector_shift);
                cmd[2] = sector_addr << (sector_shift - 8);

                if (spi_xfer(spi, cmd, 4,
                                NULL, SPI_XFER_BEGIN|SPI_XFER_END) < 0)
                        return -1;
                if (winbond_wait_ready(spi,FLASH_SECTOR_ERASE_TIMEOUT) < 0)
                        return -1;
                sector_addr ++;
        }
        return 0;
}

static int winbond_read(flash_t * fl, uint32_t offset, void *buf, size_t len)
{
        uint8_t cmd[5];
        int ret;
        spi_slave_t * spi = fl->inf.spi;

        cmd[0] = CMD_W25_FAST_READ;
        cmd[4] = 0x00; /* dummy byte */

        cmd[1] = (offset >> 16) & 0xFF;
        cmd[2] = (offset >> 8) & 0xFF;
        cmd[3] = offset & 0xFF;

        if ((ret = spi_xfer(spi, cmd, sizeof(cmd), NULL, SPI_XFER_BEGIN)) < 0) return ret;
        if ((ret = spi_xfer(spi, NULL, len, buf, SPI_XFER_END)) < 0) return ret;
        return len;
}

static int winbond_write(flash_t * fl, uint32_t offset, const void *buf, size_t len)
{
        const struct winbond_spi_flash_params *params =
                (struct winbond_spi_flash_params *)(fl->priv);
        uint8_t cmd[4];
        int actual;
        spi_slave_t * spi = fl->inf.spi;
        uint32_t byte_addr, page_addr, wlen;
        uint32_t page_shift = params->page_shift;
        uint32_t page_size = 1 << page_shift;


        byte_addr = offset & (page_size - 1);
        page_addr = offset >> page_shift;

        cmd[0] = CMD_W25_PP;
        for (actual = 0; actual < len; actual += wlen) {
                /* WEL will be forced to 0 after erased, so reset to 1 again */
                cmd[1] = CMD_W25_WREN;
                if (spi_xfer(spi, cmd + 1, 1,
                                NULL, SPI_XFER_BEGIN|SPI_XFER_END) < 0)
                        return -1;
                cmd[1] = page_addr >> 8;
                cmd[2] = page_addr;
                cmd[3] = byte_addr;

                wlen = len - actual;
                if (wlen > page_size - byte_addr) wlen = page_size - byte_addr;
                if (spi_xfer(spi, cmd, sizeof(cmd), NULL, SPI_XFER_BEGIN) < 0)
                        return -1;
                if (spi_xfer(spi, buf + actual, wlen, NULL, SPI_XFER_END) < 0) return -1;
                if (winbond_wait_ready(spi,FLASH_PROG_TIMEOUT) < 0)
                        return -1;
                page_addr ++;
                byte_addr = 0;
        }
        return len;
}

static const flashdrv_t winbond_spi_flash __flashdrv_entry= {
        .name = "winbond",
        .probe = flash_probe_winbond,
        .read = winbond_read,
        .write = winbond_write,
        .erase = winbond_erase,
};


